﻿using System;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Dynamic;

namespace Isuka.Excel.Dynamic
{
    /// <summary>
    /// A1形式またはR1C1形式のアドレスでアクセス可能にするためのメソッド
    /// インデックスアクセスの場合、R1C1形式でアクセスする
    /// </summary>
    public class DynamicXRange
        : DynamicObject
        , INotifyPropertyChanged
    {
        readonly XRange range;

        public DynamicXRange(XRange range)
        {
            Contract.Requires<ArgumentNullException>(range != null, "range");
            this.range = range;
        }

        bool TryGetRangeByIndexes(object[] indexes, out XRange result)
        {
            Contract.Ensures((Contract.Result<bool>() && Contract.ValueAtReturn(out result) != null) || !Contract.Result<bool>());
            Contract.Assume(indexes != null);
            try
            {
                if (indexes.Length == 1)
                {
                    result = range[(dynamic)indexes[0]];
                    Contract.Assume(result != null);
                    return true;
                }
                else if (indexes.Length == 2)
                {
                    result = range[(dynamic)indexes[0], (dynamic)indexes[1]];
                    Contract.Assume(result != null);
                    return true;
                }
                else
                {
                    result = null;
                    return false;
                }
            }
            catch (Exception)
            {
                result = null;
                return false;
            }
        }

        #region DynamicObject メンバー
        
        public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)
        {
            var xrange = default(XRange);
            if (TryGetRangeByIndexes(indexes, out xrange))
            {
                return DynamicUtility.AssignValue(binder, out result, xrange.Value);
            }
            else
            {
                result = null;
                return false;
            }
        }

        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            try
            {
                Contract.Assume(!string.IsNullOrEmpty(binder.Name));
                object value = range[binder.Name].Value;
                return DynamicUtility.AssignValue(binder, out result, value);
            }
            catch (Exception)
            {
                result = null;
                return false;
            }
        }

        public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value)
        {
            var result = default(XRange);
            if (TryGetRangeByIndexes(indexes, out result) && result != null)
            {
                result.Value = value;
                NotifyPropertyChanged(result.GetAddress(false, false));
                return true;
            }
            else
            {
                return false;
            }
        }

        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            try
            {
                Contract.Assume(!string.IsNullOrEmpty(binder.Name));
                var r = range[binder.Name];
                r.Value = value;
                NotifyPropertyChanged(binder.Name);
                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        #endregion

        #region INotifyPropertyChanged メンバー

        public event PropertyChangedEventHandler PropertyChanged;

        void NotifyPropertyChanged(string prpertyName)
        {
            try
            {
                var eh = PropertyChanged;
                if (eh != null)
                {
                    eh(this, new PropertyChangedEventArgs(prpertyName));
                }
            }
            catch (Exception)
            {
                // もみ消す
            }
        }

        #endregion

        #region ContractInvariantMethod

        [ContractInvariantMethod]
        [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Required for code contracts.")]
        private void ObjectInvariant()
        {
            Contract.Invariant(range != null);
        }

        #endregion

    }
}
